package net.kucoe.macarun;

import java.lang.reflect.Field;
import java.util.*;
import java.util.Map.Entry;

/**
 * Process case
 * 
 * @author Vitaliy Basyuk
 */
public class Case {
    
    private final Class<?> type;
    private final Map<String, Class<?>> fields = new HashMap<String, Class<?>>();
    private boolean breaking;
    @SuppressWarnings("rawtypes")
    private Run runner;
    @SuppressWarnings("rawtypes")
    private Check checker;
    private Map<String, Field> all;
    
    protected Case(final Class<?> type) {
        this.type = type;
    }
    
    /**
     * Returns whether object is of declared type.
     * 
     * @param object
     * @return boolean
     */
    protected boolean is(final Object object) {
        return type.isInstance(object);
    }
    
    /**
     * Returns whether case conditions were met.
     * 
     * @param object
     * @return boolean
     */
    protected boolean isTrue(final Object object) {
        if (!is(object)) {
            return false;
        }
        all = getAllFields(object.getClass());
        for (Entry<String, Class<?>> entry : fields.entrySet()) {
            Field field = all.get(entry.getKey());
            if (field == null) {
                return false;
            }
            if (!entry.getValue().isAssignableFrom(field.getType())) {
                return false;
            }
        }
        return check(object);
    }
    
    /**
     * Checks {@link Check}
     * 
     * @param object
     * @return result
     */
    @SuppressWarnings("unchecked")
    protected boolean check(final Object object) {
        if (checker != null) {
            return checker.check(object);
        }
        return true;
    }
    
    /**
     * Runs {@link Run}
     * 
     * @param object
     * @return result
     */
    @SuppressWarnings("unchecked")
    protected <T> T run(final Object object) {
        if (runner != null) {
            return (T) runner.run(object);
        }
        return null;
    }
    
    /**
     * Returns the breaking boolean.
     * 
     * @return the breaking boolean.
     */
    protected boolean isBreaking() {
        return breaking;
    }
    
    /**
     * Adds condition for field to be present.
     * 
     * @param fieldName
     * @return {@link Case}
     */
    public Case with(final String fieldName) {
        fields.put(fieldName, Object.class);
        return this;
    }
    
    /**
     * Adds condition for field to be present.
     * 
     * @param fieldName
     * @param fieldType
     * @return {@link Case}
     */
    public Case with(final String fieldName, final Class<?> fieldType) {
        fields.put(fieldName, fieldType);
        return this;
    }
    
    /**
     * Defines checking method.
     * 
     * @param checker
     * @return {@link Case}
     */
    @SuppressWarnings("rawtypes")
    public Case check(final Check checker) {
        this.checker = checker;
        return this;
    }
    
    /**
     * Defines running method.
     * 
     * @param runner
     * @return {@link Case}
     */
    @SuppressWarnings("rawtypes")
    public Case run(final Run runner) {
        this.runner = runner;
        return this;
    }
    
    /**
     * Makes case to break flow and other cases will not be checked.
     * 
     * @return {@link Case}
     */
    public Case breaking() {
        breaking = true;
        return this;
    }
    
    private Map<String, Field> getAllFields(final Class<?> clazz) {
        Map<String, Field> fields = new HashMap<String, Field>();
        Class<?> parent = clazz.getSuperclass();
        if (parent != null) {
            fields.putAll(getAllFields(parent));
        }
        for (Field field : clazz.getDeclaredFields()) {
            fields.put(field.getName(), field);
        }
        return fields;
    }
    
    protected Object get(final String fieldName, final Object object) {
        if (fields.containsKey(fieldName)) {
            Field field = all.get(fieldName);
            if (field != null) {
                return getAndCatch(field, object);
            }
        }
        return null;
    }
    
    private Object getAndCatch(final Field field, final Object object) {
        if (object != null) {
            field.setAccessible(true);
            try {
                return field.get(object);
            } catch (IllegalArgumentException e) {
                // skip
            } catch (IllegalAccessException e) {
                // skip
            }
        }
        return null;
    }
    
}
